﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SmartMathLibrary.NumericalIntegration
{
    /// <summary>
    /// This class provides the numerical integration for integrals by using the left point method.
    /// </summary>
    [Serializable]
    public class LeftPointIntegrator : AbstractCompositeIntegrator
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="LeftPointIntegrator"/> class.
        /// </summary>
        /// <param name="a">The lower value a of the integral.</param>
        /// <param name="b">The upper value b of the integral.</param>
        /// <param name="function">The function of the integral.</param>
        public LeftPointIntegrator(double a, double b, IRealFunction function)
            : base(a, b, function)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="LeftPointIntegrator"/> class.
        /// </summary>
        /// <param name="a">The lower value a of the integral.</param>
        /// <param name="b">The upper value b of the integral.</param>
        /// <param name="function">The function of the integral.</param>
        public LeftPointIntegrator(double a, double b, SimplePolynomial function)
            : base(a, b, function)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="LeftPointIntegrator"/> class.
        /// </summary>
        /// <param name="a">The lower value a of the integral.</param>
        /// <param name="b">The upper value b of the integral.</param>
        /// <param name="function">The function of the integral.</param>
        public LeftPointIntegrator(double a, double b, Polynomial function)
            : base(a, b, function)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="LeftPointIntegrator"/> class.
        /// </summary>
        /// <param name="a">The lower value a of the integral.</param>
        /// <param name="b">The upper value b of the integral.</param>
        /// <param name="function">The function of the integral.</param>
        public LeftPointIntegrator(double a, double b, SimpleRationalPolynomial function)
            : base(a, b, function)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="LeftPointIntegrator"/> class.
        /// </summary>
        /// <param name="a">The lower value a of the integral.</param>
        /// <param name="b">The upper value b of the integral.</param>
        /// <param name="function">The function of the integral.</param>
        public LeftPointIntegrator(double a, double b, HardRealFunction function)
            : base(a, b, function)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="LeftPointIntegrator"/> class.
        /// </summary>
        /// <param name="integral">The integral for the numerical integration.</param>
        public LeftPointIntegrator(FinitelyDoubleIntegral integral)
            : base(integral.A, integral.B, integral.Function)
        {
        }

        /// <summary>
        /// Integrates the integral in a numerical way. After running the numerical integration
        /// it should be checked if the result has a precision error.
        /// </summary>
        /// <param name="iterations">The maximum numbers of iterations.</param>
        /// <param name="precision">The minimum precision of the numerical integration.</param>
        /// <returns>The area of the specified integral.</returns>
        public override double Integrate(int iterations, double precision)
        {
            bool swapBounds = false;

            if (this.B<this.A)
            {
                double tempuri = this.A;

                this.A = this.B;
                this.B = tempuri;
                swapBounds = true;
            }

            int i = 2;
            double newValue = 0;
            double oldValue = this.InternalIntegrate(1);

            while (i < iterations)
            {
                newValue = this.InternalIntegrate(i);
                this.relativeError = Math.Abs(newValue - oldValue);

                if (this.relativeError < precision)
                {
                    this.neededIterations = i;
                    this.precisionError = false;

                    if (swapBounds)
                    {
                        double tempuri = this.A;

                        this.A = this.B;
                        this.B = tempuri;

                        return -newValue;
                    }

                    return newValue;
                }

                oldValue = newValue;
                i++;
            }

            this.neededIterations = i;
            this.precisionError = true;

            if (swapBounds)
            {
                double tempuri = this.A;

                this.A = this.B;
                this.B = tempuri;

                return -newValue;
            }

            return newValue;
        }

        /// <summary>
        /// Internal integration algorithm for the used integration method.
        /// </summary>
        /// <param name="parts">The number of parts to divide the area in.</param>
        /// <returns>The integrated area of the specified integral.</returns>
        private double InternalIntegrate(int parts)
        {
            double tempuri = 0.0;
            double h = (this.B - this.A) / (double) parts;

            for (int i = 0; i < parts; i++)
            {
                tempuri += this.Function.SolveAt(this.A + i * h);
            }

            return h * tempuri;
        }
    }
}